/*
 * Copyright 2021-2022 the original author and authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.edu.tjnu.tutor.support.controller;

import cn.edu.tjnu.tutor.common.annotation.Log;
import cn.edu.tjnu.tutor.common.core.controller.BaseController;
import cn.edu.tjnu.tutor.common.core.domain.AjaxResult;
import cn.edu.tjnu.tutor.common.core.domain.query.PageQuery;
import cn.edu.tjnu.tutor.common.core.domain.view.PageVO;
import cn.edu.tjnu.tutor.common.validation.groups.Insert;
import cn.edu.tjnu.tutor.common.validation.groups.Update;
import cn.edu.tjnu.tutor.system.domain.dto.ApplyDTO;
import cn.edu.tjnu.tutor.system.domain.dto.GroupDTO;
import cn.edu.tjnu.tutor.system.domain.entity.Group;
import cn.edu.tjnu.tutor.system.domain.extra.UserGroup;
import cn.edu.tjnu.tutor.system.domain.view.GroupApplyVO;
import cn.edu.tjnu.tutor.system.domain.view.GroupReplyVO;
import cn.edu.tjnu.tutor.system.domain.view.GroupVO;
import cn.edu.tjnu.tutor.system.domain.view.UserVO;
import cn.edu.tjnu.tutor.system.service.GroupService;
import cn.edu.tjnu.tutor.system.service.RoleService;
import cn.edu.tjnu.tutor.system.structure.GroupStruct;
import com.baomidou.lock.annotation.Lock4j;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

import static cn.edu.tjnu.tutor.common.constant.RoleConst.*;
import static cn.edu.tjnu.tutor.common.enums.Category.GROUP;
import static cn.edu.tjnu.tutor.common.enums.ExceptionType.*;
import static cn.edu.tjnu.tutor.common.enums.OperType.*;

/**
 * 导师小组信息控制层。
 *
 * @author 王帅
 * @since 2.0
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/group")
public class GroupController extends BaseController {

    private final GroupStruct groupStruct;
    private final RoleService roleService;
    private final GroupService groupService;

    /**
     * 导师查询所创建的小组信息。
     *
     * @param query 分页参数
     * @return 分页对象
     */
    @Secured(ROLE_TUTOR)
    @GetMapping("page")
    public AjaxResult<PageVO<GroupVO>> page(@Validated PageQuery query) {
        return pageByUserId(query, getUserId());
    }

    /**
     * 学生查询导师所创建的小组信息。
     *
     * @param query 分页参数
     * @return 分页对象
     */
    @Secured(ROLE_STUDENT)
    @GetMapping("page/{userId}")
    public AjaxResult<PageVO<GroupVO>> pageByUserId(@Validated PageQuery query,
                                                    @PathVariable Integer userId) {
        return pageSuccess(groupService.pageVO(userId, query.page()));
    }

    /**
     * 查询导师小组成员信息。
     *
     * @param query 分页参数
     * @return 分页对象
     */
    @Secured(ROLE_TUTOR)
    @GetMapping("list/tutor/{groupId}")
    public AjaxResult<PageVO<UserVO>> tutorList(@Validated PageQuery query,
                                                @PathVariable Integer groupId) {
        if (groupService.notOwn(getUserId(), groupId)) {
            return error(USER_IS_NOT_OWNED_GROUP);
        }
        return pageSuccess(groupService.pageUserVO(groupId, query.page()));
    }

    /**
     * 查询导师小组成员信息。
     *
     * @param query 分页参数
     * @return 分页对象
     */
    @Secured(ROLE_CAPTAIN)
    @GetMapping("list/captain")
    public AjaxResult<PageVO<UserVO>> captainList(@Validated PageQuery query) {
        Integer groupId = groupService.lambdaQuery()
                .select(Group::getGroupId)
                .eq(Group::getCaptainId, getUserId())
                .oneOpt()
                .map(Group::getGroupId)
                .orElse(null);
        // 小组长每个小组只有一个，并且一个学生只能做一个小组的组长。
        if (groupId == null) {
            return error(USER_IS_NOT_OWNED_GROUP);
        }
        return pageSuccess(groupService.pageUserVO(groupId, query.page()));
    }

    /**
     * 添加导师小组信息。
     *
     * @param groupDTO 导师小组信息
     * @return {@code code = 200} 添加成功，{@code code = 500} 添加失败
     */
    @Secured(ROLE_TUTOR)
    @PostMapping("save/default")
    @Log(category = GROUP, operType = INSERT)
    public AjaxResult<Void> saveDefault(@RequestBody @Validated(Insert.class) GroupDTO groupDTO) {
        return save(groupDTO, GroupService.DEFAULT);
    }

    /**
     * 添加导师小组信息。
     *
     * @param groupDTO 导师小组信息
     * @return {@code code = 200} 添加成功，{@code code = 500} 添加失败
     */
    @Secured(ROLE_INSTRUCTOR)
    @PostMapping("save/practical")
    @Log(category = GROUP, operType = INSERT)
    public AjaxResult<Void> savePractical(@RequestBody @Validated(Insert.class) GroupDTO groupDTO) {
        return save(groupDTO, GroupService.PRACTICAL);
    }

    /**
     * 添加导师小组信息。
     */
    private AjaxResult<Void> save(GroupDTO groupDTO, String type) {
        Group group = groupStruct.toEntity(groupDTO, getUserId(), type);
        if (groupService.containsName(group)) {
            return error(GROUP_NAME_ALREADY_EXISTS, groupDTO.getGroupName());
        }
        return toResult(groupService.save(group));
    }

    /**
     * 更新导师小组信息。
     *
     * @param groupDTO 导师小组信息
     * @return {@code code = 200} 更新成功，{@code code = 500} 更新失败
     */
    @Secured(ROLE_TUTOR)
    @PutMapping("update")
    @Log(category = GROUP, operType = UPDATE)
    public AjaxResult<Void> update(@RequestBody @Validated(Update.class) GroupDTO groupDTO) {
        Group group = groupStruct.toEntity(groupDTO);
        if (groupService.notOwn(getUserId(), group.getGroupId())) {
            return error(USER_IS_NOT_OWNED_GROUP);
        }
        return toResult(groupService.updateById(group));
    }

    /**
     * 删除导师小组信息。
     *
     * @param groupId 小组主键|1
     * @return {@code code = 200} 删除成功，{@code code = 500} 删除失败
     */
    @Secured(ROLE_TUTOR)
    @DeleteMapping("remove/{groupId}")
    @Log(category = GROUP, operType = DELETE)
    public AjaxResult<Void> remove(@PathVariable Integer groupId) {
        if (groupService.notOwn(getUserId(), groupId)) {
            return error(USER_IS_NOT_OWNED_GROUP);
        }
        return toResult(groupService.removeById(groupId));
    }

    /**
     * 导师绑定小组组长角色。
     *
     * @param userId  用户主键|1
     * @param groupId 小组主键|1
     * @return {@code code = 200} 绑定成功，{@code code = 500} 绑定失败
     */
    @Secured(ROLE_TUTOR)
    @PostMapping("bindCaptain/{userId}/{groupId}")
    public AjaxResult<Void> bindCaptain(@PathVariable Integer userId,
                                        @PathVariable Integer groupId) {
        if (roleService.hasRole(userId, ROLE_TEACHER)) {
            return error(ROLE_IS_NOT_ALLOWED);
        }
        if (roleService.hasRole(userId, ROLE_CAPTAIN)) {
            return error(USER_IS_ALREADY_A_CAPTAIN);
        }
        if (groupService.notIn(getUserId(), userId)) {
            return error(USER_IS_NOT_IN_THIS_GROUP);
        }
        return toResult(groupService.bindCaptain(userId, groupId));
    }

    /**
     * 导师解除小组组长角色。
     *
     * @param userId  用户主键|1
     * @param groupId 小组主键|1
     * @return {@code code = 200} 解除成功，{@code code = 500} 解除失败
     */
    @Secured(ROLE_TUTOR)
    @DeleteMapping("dumpCaptain/{userId}/{groupId}")
    public AjaxResult<Void> dumpCaptain(@PathVariable Integer userId,
                                        @PathVariable Integer groupId) {
        if (groupService.notIn(getUserId(), userId)) {
            return error(USER_IS_NOT_IN_THIS_GROUP);
        }
        return toResult(groupService.dumpCaptain(userId, groupId));
    }

    /**
     * 实习小组信息的下拉列表。
     *
     * @return 实习小组主键和名称
     */
    @Secured(ROLE_INSTRUCTOR)
    @GetMapping("selectList")
    public AjaxResult<List<Group>> selectList() {
        return success(groupService.lambdaQuery()
                .select(Group::getGroupId, Group::getGroupName)
                .eq(Group::getUserId, getUserId())
                .eq(Group::getType, GroupService.PRACTICAL)
                .list());
    }

    /**
     * 导师查看学生提交的小组申请信息。
     *
     * @param query 分页参数
     * @return 分页对象
     */
    @Secured(ROLE_TUTOR)
    @GetMapping("applyList")
    public AjaxResult<PageVO<GroupApplyVO>> applyList(@Validated PageQuery query) {
        return pageSuccess(groupService.pageApplyVO(getUserId(), query.page()));
    }

    /**
     * 学生查询导师已经通过的小组申请信息。
     *
     * @param query 分页参数
     * @return 分页对象
     */
    @Secured(ROLE_STUDENT)
    @GetMapping("replyList")
    public AjaxResult<PageVO<GroupReplyVO>> replyList(@Validated PageQuery query) {
        return pageSuccess(groupService.pageReplyVO(getUserId(), query.page()));
    }

    /**
     * 学生申请加入导师小组。
     *
     * @param applyDTO 申请信息
     * @return {@code code = 200} 申请成功，{@code code = 500} 申请失败
     */
    @Secured(ROLE_STUDENT)
    @PostMapping("apply")
    public AjaxResult<Void> apply(@Validated ApplyDTO applyDTO) {
        UserGroup entity = groupStruct.toEntity(applyDTO, getUserId());
        // 判断学生是否可以申请导师小组
        // 不可以申请小组的时候尝试更新一下申请信息
        // 如果遇到已经绑定小组再去更新的话返回 code == 500
        if (groupService.canApply(entity.getUserId(), entity.getGroupId())) {
            return toResult(groupService.updateApply(entity));
        }
        return toResult(groupService.apply(entity));
    }

    /**
     * 同意学生加入导师小组。
     *
     * @param userId  用户主键|1
     * @param groupId 小组主键|1
     * @return {@code code = 200} 同意申请成功，{@code code = 500} 同意申请失败
     */
    @Lock4j
    @Secured(ROLE_TUTOR)
    @PutMapping("agree/{userId}/{groupId}")
    public AjaxResult<Void> agree(@PathVariable Integer userId,
                                  @PathVariable Integer groupId) {
        if (groupService.isNotApplied(userId, groupId)) {
            return error(GROUP_CANNOT_BE_APPLIED);
        }
        if (!groupService.agree(userId, groupId)) {
            return error(GROUP_MEMBER_IS_FULL);
        }
        return AjaxResult.SUCCESS;
    }

    /**
     * 拒绝学生加入导师小组。
     *
     * @param userId  用户主键|1
     * @param groupId 小组主键|1
     * @return {@code code = 200} 拒绝申请成功，{@code code = 500} 拒绝申请失败
     */
    @Secured(ROLE_TUTOR)
    @DeleteMapping("reject/{userId}/{groupId}")
    public AjaxResult<Void> reject(@PathVariable Integer userId,
                                   @PathVariable Integer groupId) {
        if (groupService.isNotApplied(userId, groupId)) {
            return error(GROUP_CANNOT_BE_APPLIED);
        }
        return toResult(groupService.reject(userId, groupId));
    }

}